/**
 * 简单定义几种消息类型
 */
let MSG_TYPE = {
    "string": 0, "number": 1, "image": 2
};

let ws = new WebSocket("ws://localhost:8080/chatroom");
// 设置接收的二进制类型
ws.binaryType = "arraybuffer";

// 接收消息时执行
ws.onmessage = function (res) {
    console.log("收到消息 => ", new Uint8Array(res.data));

    if(res.data instanceof ArrayBuffer) {
        let messages = decode(res.data);

        console.log("解码后的消息 => ", messages);

        messages.forEach(m => {
            // 获取组件
            let comp = $(`#${format(m.id, 3, 0)}`);

            // 由组件决定如何处理消息
            if(m.id > 0 && m.id < 6) {
                comp.val(m.content);
            } else if(m.id === 7) {
                switch (m.type) {
                    case 0:
                        comp.append(`<p>${m.content}</p>`);
                        break;
                    case 1:
                        break;
                    case 2:
                        // 图片的内容是Uint8Array，需要转成Blob
                        let blobstr = window.URL.createObjectURL(new Blob([m.content]));
                        let img = document.createElement("img");
                        img.src = blobstr;
                        img.width = 100;
                        img.onload = function () {
                            window.URL.revokeObjectURL(blobstr);
                        };
                        comp.append(img);
                        break;
                }
            }
        });
    }
};
// 建立连接时执行
ws.onopen = function() {

};
// 连接关闭时执行
ws.onclose = function() {

};
// 发生错误时执行
ws.onerror = function() {

};


/**
 * 计算器
 */
$("input[flg='arg']").change(function () {
    console.log("------- 计算器 -------");

    let msg;
    if(hasValue("input[flg='arg']", "number")) {
        let a = encode("001", "number", $("#001").val());
        let b = encode("002", "number", $("#002").val());
        let c = encode("003", "number", $("#003").val());
        msg = concat(null, a, b, c);
    } else {
        msg = encode(this.id, "number", this.value);
    }

    console.log("编码后的消息 => ", msg);
    // 发送消息
    ws.send(msg);
});

/**
 * 字符拼接
 */
$("input[flg='txt']").change(function () {
    console.log("------- 字符拼接 -------");

    let msg;
    if(hasValue("input[flg='txt']", "text")) {
        let a = encode("004", "string", $("#004").val());
        let b = encode("005", "string", $("#005").val());
        let c = encode("006", "string", $("#006").val());
        msg = concat(null, a, b, c);
    } else {
        msg = encode(this.id, "string", this.value);
    }

    console.log("编码后的消息 => ", msg);
    // 发送消息
    ws.send(msg);
});

/**
 * 发送聊天信息
 */
$("#send").click(function () {
    let textarea = document.querySelector(".textarea");
    console.log(textarea);
    console.log("输入框内容 => ", textarea.innerText);

    let text = textarea.innerText;
    let html = textarea.innerHTML;

    // 获取img标签
    let imgs = html.match(/<img.*?(?:>|\/>)/gi);
    // 获取src
    let srcs = [];
    let images = [];
    for (let i = 0; imgs && i < imgs.length; i++) {
        srcs.push(imgs[i].match(/src=[\'\"]([^\'\"]*)[\'\"]/i)[1]);
        images.push(dataURL2Uint8Array(srcs[i]));
    }

    // 整合文本和图片
    let data = Uint8Array.of();
    if(text !== "") {
        data = concat(null, data, encode("007", "string", text));
    }
    if(images && images.length > 0) {
        images.forEach(m => {
            data = concat(null, data, encode("007", "image", m));
        });
    }

    // 发送消息
    ws.send(data);

    // 清空输入框
    textarea.innerText = "";
    textarea.innerHTML = "";
});

/**
 * 选择图片
 */
$("input[type='file']").change(function() {
    let files = this.files;

    for (let i = 0; i < files.length; i++) {
        let reader = new FileReader();
        reader.onload = function (e) {
            let img = `<img src="${e.target.result}" width="100px">`;
            $(".chat-typing .textarea").append(img);
        };
        reader.readAsDataURL(files[i]);
    }
});


/**
 * 格式化：添加前缀
 *
 * @param number    数字
 * @param length    字符长度
 * @param prefix    前缀
 * @returns {string}
 */
function format(number, length, prefix) {
    let str = "" + number;
    while(str.length < length) {
        str = prefix + str;
    }
    return str;
}

/**
 * 判断指定的选择器是否全部都有值
 *
 * @param selector 选择器
 * @param type      值类型
 * @returns {boolean}
 */
function hasValue(selector, type) {
    let eles = document.querySelectorAll(selector);

    if(type === "number") {
        for (let i = 0; i < eles.length; ++i) {
            if (isNaN(parseInt(eles[i].value))) {
                return false;
            }
        }
    } else if(type === "text") {
        for (let i = 0; i < eles.length; ++i) {
            if (eles[i].value === "") {
                return false;
            }
        }
    }
    return true;
}

/**
 * 编码
 *
 * 数据格式：
 *  ————————————————————————————————————————————————————————————
 * | 组件标识：2字节 | 消息类型：1字节 | 消息长度：4字节 | 消息内容 |
 * ————————————————————————————————————————————————————————————
 *
 * - 组件标识：用于确定接收消息的组件
 * - 消息类型：用于决定如何编码和解码消息内容，例如：
 *   - 字符串：编码时调用stob(content)，把内容转成二进制，
 *            解码时调用btos(bytes)，把内容解析成字符串
 *   - 数值：编码时调用ntob(number)，把十进制数转成二进制
 *          解码时调用bton(bytes)，把二进制数转成十进制
 *   - 图片：内容已经是二进制，无需再次处理
 *
 * - 消息长度
 * - 消息内容
 *
 * 可能还需要几个字节作为标识，告诉服务器如何处理消息，暂时不清楚
 *
 */
function encode(componentId, messageType, messageContent) {
    // 组件id：2个字节，可表示65535个组件   【 需要有个组件id和数值的映射规则】
    // 暂时以："001" => 1   "002" => 2 这样的映射规则
    let id = new DataView(new ArrayBuffer(2));
    id.setUint16(0, parseInt(componentId));
    id = new Uint8Array(id.buffer);

    // 消息类型：1个字节，可以表示256种消息  【 需要有个消息类型和数值的映射规则】
    // 暂时以：{"string": 0, "number": 1, "image": 2} 这样的映射规则
    let type = Uint8Array.of(MSG_TYPE[messageType]);

    // 消息内容
    let content;

    switch (messageType) {
        case "string":
            content = stob(messageContent);
            break;
        case "number":
            content = ntob(messageContent);
            break;
        default:
            content = messageContent;
    }


    // 消息长度
    let length = new DataView(new ArrayBuffer(4));
    length.setUint32(0, content.byteLength);
    length = new Uint8Array(length.buffer);

    let data = concat(null, id, type, length, content);
    console.log("编码后的消息 => ", data);

    return data;
}

/**
 * 解码
 *
 * @param {ArrayBuffer} bytes
 */
function decode(bytes) {
    if(!bytes instanceof ArrayBuffer) {
        return;
    }

    let messages = [], offset = 0,
        view = new DataView(bytes),
        ui8 = new Uint8Array(bytes);

    while(offset < view.byteLength) {
        // 组件id
        let id = view.getUint16(offset);
        offset += 2;

        // 消息类型
        let type = view.getUint8(offset++);
        let length = view.getUint32(offset);
        offset += 4;

        // 消息内容
        let content = ui8.slice(offset, offset + length);
        offset += length;
        // 根据类型解析内容
        switch (type) {
            case 0:
                content = btos(content);
                break;
            case 1:
                content = bton(content);
        }

        messages.push({id, type, content});
    }

    return messages;
}




